关于 java 深拷贝中的 ClassLoader

问题场景

在使用 java 序列化进行深拷贝时,出现了类异常,现在将代码简化后进行复现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.util.SerializationUtils;

import java.io.Serializable;
import java.util.concurrent.TimeUnit;

/**
* @author cakipaul
*/
@SpringBootApplication
public class HackthonApplication {

interface Inter extends Serializable {
String out();
}

public static class Impl implements Inter {
@Override
public String out() {
Class<?> clzOfClzLoader = this.getClass().getClassLoader().getClass();
return "out from " + clzOfClzLoader.getName() + "@" + clzOfClzLoader.hashCode();
}
}

public static void main(String[] args) {
SpringApplication.run(HackthonApplication.class, args);
Impl impl = new Impl();
try {
System.out.println(">>>>");
System.out.println(impl.out());
Object copy = SerializationUtils.deserialize(SerializationUtils.serialize(impl));
System.out.println(((Inter) copy).out());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

如果将以上代码中 SpringApplication.run(HackthonApplication.class, args); 注释掉,运行结果是:

1
2
out from sun.misc.Launcher$AppClassLoader@349885916
out from sun.misc.Launcher$AppClassLoader@349885916

但如果加入 SpringApplication.run(HackthonApplication.class, args); ,运行时则会抛出异常:

1
2
3
4
5
6
7
8
9
Exception in thread "restartedMain" java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49)
Caused by: java.lang.ClassCastException: com.tangguo.hackthon.HackthonApplication$Impl cannot be cast to com.tangguo.hackthon.HackthonApplication$Inter
at com.tangguo.hackthon.HackthonApplication.main(HackthonApplication.java:38)
... 5 more

进行 debug 调试,发现其中原对象 impl 的 classloader 的类是 org.springframework.boot.devtools.restart.classloader.RestartClassLoader ,而深拷贝出的 copy 的类的 classloader 的类是 sun.misc.Launcher$AppClassLoader 。原因是 gradle 依赖里添加了 `spring-boot-devtools :

1
developmentOnly 'org.springframework.boot:spring-boot-devtools'`

取消该依赖后问题消失。

追溯问题

查看 SerializationUtils.deserialize 源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Nullable
public static Object deserialize(@Nullable byte[] bytes) {
if (bytes == null) {
return null;
}
try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes))) {
return ois.readObject();
}
catch (IOException ex) {
throw new IllegalArgumentException("Failed to deserialize object", ex);
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException("Failed to deserialize object type", ex);
}
}

发现其中使用的原生 ObjectInputStream 。查看 readObject 方法,可以看到输出 object 的 class 在 resolveClass 方法中获取(代理类通过 resolveProxyClass 方法获取):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
protected Class<?> resolveClass(ObjectStreamClass desc)
throws IOException, ClassNotFoundException
{
String name = desc.getName();
try {
return Class.forName(name, false, latestUserDefinedLoader());
} catch (ClassNotFoundException ex) {
Class<?> cl = primClasses.get(name);
if (cl != null) {
return cl;
} else {
throw ex;
}
}
}

其中 latestUserDefinedLoader() 方法的返回值即反序列化的对象的类的类加载器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private static ClassLoader latestUserDefinedLoader() {
return sun.misc.VM.latestUserDefinedLoader();
}
/**
* sun.misc.VM
*/
public static native ClassLoader latestUserDefinedLoader0();
public static ClassLoader latestUserDefinedLoader() {
ClassLoader loader = latestUserDefinedLoader0();
if (loader != null) {
return loader;
}
try {
return Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException e) {
return null;
}
}

该 native 方法的注释如下:

1
2
3
4
5
6
7
8
9
* Returns first non-privileged class loader on the stack (excluding
* reflection generated frames) or the extension class loader if only
* class loaded by the boot class loader and extension class loader are
* found on the stack.

返回栈上的第一个非特权类装入器(不包括
反射生成的帧)或扩展类装入器
由引导类装入器和扩展类装入器装入的类为
在栈上找到的。

解决

使用指定 ClassLoader 的 OnjectIutputStream 即可,推荐使用 org.springframework.core.ConfigurableObjectInputStream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import org.springframework.core.ConfigurableObjectInputStream;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;

import java.io.*;

/**
* @author cakipaul
* @date 2021/10/19 9:38 下午
**/
@Component
public class CloneUtil {
public static Object clone(Object obj) {
return deserialize(serialize(obj));
}

@Nullable
public static byte[] serialize(@Nullable Object object) {
if (object == null) {
return null;
}
ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
try (ObjectOutputStream oos = new ObjectOutputStream(baos)) {
oos.writeObject(object);
oos.flush();
}
catch (IOException ex) {
throw new IllegalArgumentException("Failed to serialize object of type: " + object.getClass(), ex);
}
return baos.toByteArray();
}

/**
* deserialize with current class loader
*/
@Nullable
public static Object deserialize(@Nullable byte[] bytes) {
if (bytes == null) {
return null;
}
try (ObjectInputStream ois = new ConfigurableObjectInputStream(
new ByteArrayInputStream(bytes),
CloneUtil.class.getClassLoader())) {
return ois.readObject();
} catch (IOException ex) {
throw new IllegalArgumentException("Failed to deserialize object", ex);
} catch (ClassNotFoundException ex) {
throw new IllegalStateException("Failed to deserialize object type", ex);
}
}

}